package com.gusto.engine.semsim.measures.impl;

import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;

import net.didion.jwnl.JWNL;

import org.apache.log4j.Logger;

import shef.nlp.wordnet.similarity.SimilarityMeasure;

import com.gusto.engine.semsim.exceptions.SimilarityException;
import com.gusto.engine.semsim.measures.ValueSimilarity;

/**
 * <p>Calculates the similarity between two concepts of the WordNet ontology.<br/>
 * It uses the <a href="http://nlp.shef.ac.uk/result/software.html">RESuLT API</a>.<br/>
 * The only supported measure is JCn and Lin.</p>
 * 
 * @author amokrane.belloui@gmail.com
 */
public class WordNetSimilarity implements ValueSimilarity {
	
	private Logger log = Logger.getLogger(WordNetSimilarity.class);
	
	private boolean firstWordOnly = false;
	
	private String PARAM_WORDNET_CONFIG = "";
	private String PARAM_INFOCONTENT = "";
	private String PARAM_MAPPING = "";
	private SimilarityMeasure wordnetSim;
	
	public String getId() {
		String id = "[" + getClass().getName() + "|" + firstWordOnly + "|" + 
			PARAM_WORDNET_CONFIG + "|" + PARAM_INFOCONTENT + "|" + PARAM_MAPPING + "]";
		return id;
	}
	
	public WordNetSimilarity() {
	}
	
	public WordNetSimilarity(String PARAM_WORDNET_CONFIG, String PARAM_INFOCONTENT, String PARAM_MAPPING) throws SimilarityException {
		this.PARAM_WORDNET_CONFIG = PARAM_WORDNET_CONFIG;
		this.PARAM_INFOCONTENT = PARAM_INFOCONTENT;
		this.PARAM_MAPPING = PARAM_MAPPING;
		try {
			log.debug("Initializing Wordnet Similarity");
			//Initialize WordNet - this must be done before you try and create a similarity measure otherwise nasty things might happen!
			
			JWNL.initialize(new FileInputStream(this.PARAM_WORDNET_CONFIG));
			
			//Create a map to hold the similarity config params
			Map<String,String> params = new HashMap<String,String>();
			
			//the simType parameter is the class name of the measure to use
			params.put("simType", "shef.nlp.wordnet.similarity.JCn");
			
			//this param should be the URL to an infocontent file (if required by the similarity measure being loaded)
			params.put("infocontent", this.PARAM_INFOCONTENT);
			
			//this param should be the URL to a mapping file if the user needs to make synset mappings
			params.put("mapping", this.PARAM_MAPPING);
			
			//create the similarity measure
			wordnetSim = SimilarityMeasure.newInstance(params);
			
			log.debug("Wordnet Similarity Initialized");
		}
		catch (Exception ex) {
			log.error("Error initializing Wordnet Similarity");
			throw new SimilarityException(ex);
		}
	}
	
	public Double getSimilarity(Object value1, Object value2) throws SimilarityException {
		try {
			//Get two words from WordNet
			try {
				Long start = System.currentTimeMillis();
				
				Double sim = wordnetSim.getSimilarity(transform((String)value1), transform((String)value2)).getSimilarity();
				log.debug("WordNet executed in " + (System.currentTimeMillis() - start));
				// Normalization
				if (sim > 1) {
					log.debug("WordNet (" + value1 + ", " + value2 + ") " + sim + "");
					sim = 1.0;
				}
				log.info("WordNet (" + value1 + ", " + value2 + ") " + sim + "");
				return sim;
			}
			catch (Exception ex) {
				log.info("WordNet (" + value1 + ", " + value2 + ") 0.0, because of an Exception " + ex);
				log.error(ex, ex);
				return 0.0;
			}
			//return sim.getSimilarity(value1.getString(), value2.getString()).getSimilarity();
		}
		catch (Exception ex) {
			throw new SimilarityException(ex);
		}
	}
	/*
	public static void main(String[] args) throws Exception {
		WordNetSimilarity s = new WordNetSimilarity();
		s.setFirstWordOnly(true);
		log.debug(s.transform(" realisation film"));
	}
	*/
	
	private String transform(String str) {
		str = str.trim();
		if (this.isFirstWordOnly()) {
			int i = str.indexOf(' ');
			if (i > 0) {
				return str.substring(0, i);
			}
		}
		return str;
	}
	
	public boolean isFirstWordOnly() {
		return firstWordOnly;
	}
	public void setFirstWordOnly(boolean firstWordOnly) {
		this.firstWordOnly = firstWordOnly;
	}

	public void setPARAM_WORDNET_CONFIG(String param_wordnet_config) {
		PARAM_WORDNET_CONFIG = param_wordnet_config;
	}
	
	public void setPARAM_INFOCONTENT(String param_infocontent) {
		PARAM_INFOCONTENT = param_infocontent;
	}
	
	public void setPARAM_MAPPING(String param_mapping) {
		PARAM_MAPPING = param_mapping;
	}
	
}
